package net.cyndeline.entitySystem.defaultImpl

import net.cyndeline.entitySystem.Component
import language.postfixOps

/**
 * Used to store a number of component sets, indicating which components
 * an eventhandler system is interested in. Components can be filtered
 * in three ways:
 * 
 * <ul>
 * <li> getAll - Requires an entity to contain every specified component
 * <li> getSome - Requires at least on of the specified components to be present.
 * <li> exclude - Excludes any entity that contains one of the specified components.
 * </ul>
 * 
 * It's also possible to chain multiple filters to receive an aspect that 
 * contains them all.
 * 
 * <p>
 * Usage example, creating a new aspect: 
 * 
 * Aspect.getAll(classOf[SomeComponent], classOf[OtherComponent])
 * 		 .getSome(classOf[Contains], classOf[OneOf], classOf[These])
 *    	 .exclude(classOf[ExludeEntitiesWithThisComponent])
 *      
 * Note that you cannot call these methods more than once per aspect.
 * </p>
 */
class Aspect private (val all: Set[Class[_ <: Component]], 
					  val some: Set[Class[_ <: Component]], 
					  val exclud: Set[Class[_ <: Component]]) {
  val allSomeIntersect = (all & some)
  val allExcludeIntersect = (all & exclud)
  val someExcludeIntersect = (some & exclud)
  
  /* Make sure two sets doesn't contain the same element. */
  if (allSomeIntersect nonEmpty) {
    componentInMultipleCollections(allSomeIntersect)
    
  } else if (allExcludeIntersect nonEmpty) {
    componentInMultipleCollections(allExcludeIntersect)
    
  } else if (someExcludeIntersect nonEmpty) {
    componentInMultipleCollections(someExcludeIntersect)
  }
  
  /**
   * The only constructor available for users. Call getter methods to
   * add filters.
   */
  def this() = this(Set(), Set(), Set())
  
  /**
   * Specifies that all components should be present in an entity.
   * @param components A list of components, duplicates will not be added
   * 				   to the aspect.
   * @return This aspect with the added component filter.
   */
  def getAll(components: Class[_ <: Component]*): Aspect = {
    val componentSet: Set[Class[_ <: Component]] = Set(components.toArray: _*)
    
    if (all nonEmpty) {
      presentCollectionException
      
    } else if (componentSet isEmpty) {
      emptyAspectAddition
      
    } else {
      new Aspect(componentSet, some, exclud)
    }
  }
  
  /**
   * Specifies that at least one or more components should be present in an
   * entity.
   * @param components A list of components, duplicates will not be added
   * 				   to the aspect.
   * @return This aspect with the added component filter.
   */
  def getSome(components: Class[_ <: Component]*): Aspect = {
     val componentSet: Set[Class[_ <: Component]] = Set(components.toArray: _*)
     
    if (some nonEmpty) {
      presentCollectionException
      
    } else if (components isEmpty) {
      emptyAspectAddition
      
    } else {
      new Aspect(all, componentSet, exclud)
    }
  }
  
  /**
   * Specifies that none of the components should be present in an entity.
   * @param components A list of components, duplicates will not be added
   * 				   to the aspect.
   * @return This aspect with the added component filter.
   */
  def exclude(components: Class[_ <: Component]*): Aspect = {
    val componentSet: Set[Class[_ <: Component]] = Set(components.toArray: _*)
    
    if (exclud nonEmpty) {
      presentCollectionException
      
    } else if (components isEmpty) {
      emptyAspectAddition
      
    } else {
      new Aspect(all, some, componentSet)
    }
  }
  
  private def presentCollectionException = {
    throw new IllegalArgumentException("Each aspect filter can only be set once.")
  }
  
  private def emptyAspectAddition = {
    throw new IllegalArgumentException("No components were added to the filer.")
  }
  
  private def componentInMultipleCollections(components: Set[Class[_ <: Component]]) = {
    throw new IllegalArgumentException("One or more components is present in more than one filter: " + components.toString)
  }
}